﻿using System;
using System.Collections.Generic;

namespace battlenet.wow.rt.sdk.utility
{
    internal interface IRemapper<out T>
    {
        /// <summary>
        /// Remaps the specified input.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        T Remap(object input);
    }

    /// <summary>
    /// Base class for remapper classes.
    /// </summary>
    /// <typeparam name="T">The intended output type</typeparam>
    internal abstract class Remapper<T> : IRemapper<T>
    {
        private Dictionary<Type, Func<object, T>> _mappers;

        /// <summary>
        /// Gets the mappers.
        /// </summary>
        /// <value>
        /// The mappers.
        /// </value>
        protected Dictionary<Type, Func<object, T>> Mappers
        {
            get
            {
                if (_mappers != null) return _mappers;
                _mappers = new Dictionary<Type, Func<object, T>>();
                RegisterMappers();
                return _mappers;
            }
        }

        /// <summary>
        /// Remaps the specified input.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        public virtual T Remap(object input)
        {
            try
            {
                return input == null ? default(T) : Mappers[input.GetType()](input);
            }
            catch (KeyNotFoundException ex)
            {
                if (input != null)
                    throw new KeyNotFoundException(
                        string.Format("The key \"{0}\" was not found in the mapper dictionary", input.GetType()), ex);
                else throw;
            }
        }

        /// <summary>
        /// Registers the mappers.
        /// </summary>
        public abstract void RegisterMappers();

        /// <summary>
        /// Tries the cast.
        /// </summary>
        /// <typeparam name="TExpected">The type of the expected.</typeparam>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException"></exception>
        protected TExpected TryCast<TExpected>(object input)
            where TExpected : class
        {
            var obj = input as TExpected;
            if (obj == null)
            {
                throw new ArgumentException(
                    string.Format("Input was of type {0}. Expected {1}", input.GetType(), typeof(TExpected)));
            }

            return obj;
        }

        /// <summary>
        /// Registers the specified input.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="action">The action.</param>
        protected void Register(Type input, Func<object, T> action)
        {
            Mappers.Add(input, action);
        }
    }
}
